package com.gao.security.autoconfig.verificationcode;

import com.gao.security.autoconfig.verificationcode.properties.RegisterUrlInfo;
import com.gao.security.autoconfig.verificationcode.properties.ValidateCodeProperties;
import com.gao.security.verificationcode.code.VerificationCodeGenerationStrategy;
import com.gao.security.verificationcode.common.RequestPathType;
import com.gao.security.verificationcode.entity.VerificationCode;
import com.gao.security.verificationcode.event.ValidateCodeRemoveEvent;
import com.gao.security.verificationcode.exception.ValidateCodeIncorrectException;
import com.gao.security.verificationcode.exception.ValidateCodeTimeoutException;
import com.gao.security.verificationcode.key.DefalutKeyGenerationStrategy;
import com.gao.security.verificationcode.key.KeyGenerationStrategy;
import com.gao.security.verificationcode.storage.InMemoryVerificationCodeOperationManager;
import com.gao.security.verificationcode.storage.VerificationCodeStorageManager;
import com.gao.security.verificationcode.web.verificate.VerificationCodeManager;
import com.google.common.collect.Maps;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotNull;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * <p></p>
 *
 * @author GaoShuangWen
 * @since 2019-08-01 11:30
 */

public class DefaultVerificationCodeManager implements VerificationCodeManager, ApplicationContextAware {

    private ApplicationContext applicationContext;
    /**
     * 保存接口实现类名及对应的类
     */
    private Map<RequestPathType, RequestMatcherStrategyAggregation> beanMap;

    private VerificationCodeStorageManager operationManager ;

    private KeyGenerationStrategy generationStrategy = new DefalutKeyGenerationStrategy();


    @Override
    public void send(HttpServletRequest request, HttpServletResponse response) throws IOException {
        // 检查是否需要过滤的地址
        RequestMatcherStrategyAggregation aggregation = requiresSendCheck(request);
        if (aggregation == null) {
            return;
        }
        VerificationCodeGenerationStrategy strategy = aggregation.getStrategy();
        String code = strategy.createCode();
        VerificationCode verificationCode =
                new VerificationCode(code, generationStrategy.key("123"), 1);
        operationManager.saveCode(verificationCode);
        strategy.sendCode(request, response, verificationCode);
    }

    @Override
    public void verification(HttpServletRequest request) throws AuthenticationException {
        RequestMatcherStrategyAggregation aggregation = requiresNeedCheck(request);
        if (aggregation == null) {
            return;
        }
        VerificationCodeGenerationStrategy strategy = aggregation.getStrategy();
        VerificationCode check = strategy.check(request);
        VerificationCode codeObject = operationManager.getCode(check.getCodeToken());
        if (codeObject == null || System.currentTimeMillis() < codeObject.getExpireTime()) {
            ValidateCodeRemoveEvent event=new ValidateCodeRemoveEvent(check.getCodeToken());
            applicationContext.publishEvent(event);
            throw new ValidateCodeTimeoutException("ValidateCode timeout");
        }
        if (!check.getCode().equals(codeObject.getCode())) {
            ValidateCodeRemoveEvent event=new ValidateCodeRemoveEvent(check.getCodeToken());
            applicationContext.publishEvent(event);
            throw new ValidateCodeIncorrectException("ValidateCode error");
        }

    }


    private RequestMatcherStrategyAggregation requiresSendCheck(HttpServletRequest request) throws AuthenticationException {
        Set<Map.Entry<RequestPathType, RequestMatcherStrategyAggregation>> entries = beanMap.entrySet();
        List<RequestMatcherStrategyAggregation> collect = entries.stream().filter(aggregationEntry -> aggregationEntry.getValue().getSendCodeRequestMatcher().matches(request))
                .map(Map.Entry::getValue).collect(Collectors.toList());
        return collect != null && collect.size() > 0 ? collect.get(0) : null;
    }


    public RequestMatcherStrategyAggregation requiresNeedCheck(HttpServletRequest request) throws AuthenticationException {
        Set<Map.Entry<RequestPathType, RequestMatcherStrategyAggregation>> entries = beanMap.entrySet();
        List<RequestMatcherStrategyAggregation> collect = entries.stream().filter(aggregationEntry -> aggregationEntry.getValue().getNeedCheckCodeRequestMatcher().matches(request))
                .map(Map.Entry::getValue).collect(Collectors.toList());
        return collect != null && collect.size() > 0 ? collect.get(0) : null;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        Map<String, VerificationCodeGenerationStrategy> beansOfType = applicationContext.getBeansOfType(VerificationCodeGenerationStrategy.class);
        ValidateCodeProperties properties = applicationContext.getBean(ValidateCodeProperties.class);
        Map<RequestPathType, RequestMatcherStrategyAggregation> bean = Maps.newHashMap();
        beansOfType.forEach((s, strategy) -> {
            RequestPathType requestPathType = strategy.requestPathType();
            switch (requestPathType) {
                case IMG:
                    bean.put(RequestPathType.IMG, returnAggregation(strategy, properties.getImg()));
                    break;
                case SMS:
                    bean.put(RequestPathType.SMS, returnAggregation(strategy, properties.getSms()));
                    break;
                default:
                    bean.put(RequestPathType.EMAIL, returnAggregation(strategy, properties.getEmail()));
                    break;
            }
        });
        this.applicationContext=applicationContext;
        this.beanMap = bean;
    }

    private RequestMatcherStrategyAggregation returnAggregation(VerificationCodeGenerationStrategy strategy, RegisterUrlInfo registerUrlInfo) {
        RequestMatcher sendCodeRequestMatcher = new AntPathRequestMatcher(registerUrlInfo.getSendUrl(), "POST");
        RequestMatcher needCheckCodeRequestMatcher = new AntPathRequestMatcher(registerUrlInfo.getLoginUrl(), "POST");
        return new RequestMatcherStrategyAggregation(strategy, sendCodeRequestMatcher, needCheckCodeRequestMatcher);
    }

    public void setOperationManager(VerificationCodeStorageManager operationManager) {
        this.operationManager = operationManager;
    }

    public void setGenerationStrategy(KeyGenerationStrategy generationStrategy) {
        this.generationStrategy = generationStrategy;
    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    private class RequestMatcherStrategyAggregation {

        @NotNull
        VerificationCodeGenerationStrategy strategy;

        /**
         * 发送code的url匹配
         */
        @NotNull
        RequestMatcher sendCodeRequestMatcher;
        /**
         * 需要校验code的url匹配
         */
        @NotNull
        RequestMatcher needCheckCodeRequestMatcher;
    }


}
